#ifndef INPUT_FEATURE_CC
#define INPUT_FEATURE_CC
#include<string>
#include<fstream>
#include<sstream>
#include<iostream>
#include<algorithm>
#include<iterator>
#include<vector>
#include<queue>
#include<unordered_map>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include "def.hpp"
#include "input_feature.hpp"
#include "utils.hpp"
#include "log.hpp"

using namespace std;

namespace Puza{
	PuzaInputFeature* PuzaInputFeature::m_pInstance = NULL;

  	PuzaInputFeature* PuzaInputFeature::Instance(){
  		if (!m_pInstance)   // Only allow one instance of class to be generated.
  			m_pInstance = new PuzaInputFeature;
  		return m_pInstance;
  	}

  	PuzaInputFeature::PuzaInputFeature(){ }

  	void PuzaInputFeature::Read_Session(){
  		string temp;
  		string input_file_name = PuzaDEF::Instance()->TRAIN_FILE_NAME;
  		ifstream InputFile;
  		int TOTAL_N = 0;
  		int POSITIVE_N = 0;
  		int NEGATIVE_N = 0;
  		string last_session_id = "";

  		PuzaLogger::Instance()->PutString("Input File:" + input_file_name);

  		if(input_file_name.empty()){
  			cout << "Please give input file name." << endl;
  			exit(0);
  		}

  		InputFile.open(input_file_name.c_str());
  		if(!InputFile.good()){
  			cout << "Please give the correct file name." << endl;
  			exit(0);
  		}

  		// clear
  		PuzaDEF::Instance()->INPUT_SESSION_TABLE.clear();
  		PuzaDEF::Instance()->FEATURE_MAPPING.clear();
  		unordered_map<string,int>::iterator map_iter;
  		string raw_last_session;
  		int current_session_id = -1;

  		while(!InputFile.eof()){
  			getline(InputFile,temp,'\n');
  			if(temp !=""){
  				PuzaRow newRow;
  				// read response
  				std::istringstream iss(temp);
  				getline(iss,temp,' ');
  				string raw_current_session = temp;
  				if(raw_current_session.compare(raw_last_session)!=0){
  					// new session detected
  					PuzaSession newSession;
  					PuzaDEF::Instance()->INPUT_SESSION_TABLE.push_back(newSession);
  					current_session_id = PuzaDEF::Instance()->INPUT_SESSION_TABLE.size();
  					PuzaDEF::Instance()->INPUT_SESSION_TABLE[current_session_id].positive_rows.clear();
  					PuzaDEF::Instance()->INPUT_SESSION_TABLE[current_session_id].negative_rows.clear();
  				}
  				getline(iss,temp,' ');
  				newRow.response = from_string<double>(temp); // convert, require c++11
  				getline(iss,temp,'\n');
  				std::istringstream tempiss(temp);
  				vector<string> tokens;
  				copy(istream_iterator<string>(tempiss), istream_iterator<string>(), back_inserter<vector<string> >(tokens));
  				if(tokens.size()<1){
  					continue;
  				}
  				for (vector<string>::iterator iter = tokens.begin(); iter < tokens.end(); iter++){
  					string raw_feature_id;
  				    PuzaRowItem newItem;
  				    std::istringstream valueiss((*iter));
  				    getline(valueiss,temp,':');
  				    raw_feature_id = temp;
  				    getline(valueiss,temp);
  				    double feature_value = from_string<double>(temp);
  				    // check feature mapping
  				    int target_feature_id = -1;
  				    map_iter = PuzaDEF::Instance()->FEATURE_MAPPING.find(raw_feature_id); // check and insert user id
  				    int USER_NUM = PuzaDEF::Instance()->FEATURE_MAPPING.size();
  				    if(map_iter == PuzaDEF::Instance()->FEATURE_MAPPING.end()){
  				    	PuzaDEF::Instance()->FEATURE_MAPPING.insert(pair<string,int>(raw_feature_id, USER_NUM));
  				    	target_feature_id = USER_NUM;
  				    }
  				    else{
  				    	target_feature_id = (*map_iter).second;
  				    }
  				    newItem.feature_id = target_feature_id;
  				    newItem.feature_value = feature_value;
  				    newRow.features.push_back(newItem);
  				}
  				if(newRow.response > 0){
  					PuzaDEF::Instance()->INPUT_SESSION_TABLE[current_session_id].positive_rows.push_back(newRow);
  					POSITIVE_N ++;
  				}
  				else{
  					PuzaDEF::Instance()->INPUT_SESSION_TABLE[current_session_id].negative_rows.push_back(newRow);
  					NEGATIVE_N ++;
  				}
  				TOTAL_N ++;
  				if(TOTAL_N % 1000000 == 0){
  					PuzaLogger::Instance()->PutString("Processed " + to_string<int>(TOTAL_N) + " rows.");
  				}
  			}
  		}
  		InputFile.close();
  		PuzaLogger::Instance()->PutString("Finished Reading File.");
  		PuzaLogger::Instance()->PutString("Total number of sessions:" + to_string<int>(PuzaDEF::Instance()->INPUT_SESSION_TABLE.size()));
  		PuzaLogger::Instance()->PutString("Total number of rows:" + to_string<int>(TOTAL_N));
  		PuzaLogger::Instance()->PutString("Total number of positive rows:" + to_string<int>(POSITIVE_N));
  		PuzaLogger::Instance()->PutString("Total number of negative rows:" + to_string<int>(NEGATIVE_N));
  		PuzaLogger::Instance()->PutString("Total number of features:" + to_string<unsigned int>(PuzaDEF::Instance()->FEATURE_MAPPING.size()));
  	}


	void PuzaInputFeature::Read_Simple(){
		string temp;
		string input_file_name = PuzaDEF::Instance()->TRAIN_FILE_NAME;
		ifstream InputFile;
		int TOTAL_N = 0;

		PuzaLogger::Instance()->PutString("Input File:" + input_file_name);

		if(input_file_name.empty()){
		      cout << "Please give input file name." << endl;
		      exit(0);
		}

		InputFile.open(input_file_name.c_str());
		if(!InputFile.good()){
		      cout << "Please give the correct file name." << endl;
		      exit(0);
		}

		// clear
		PuzaDEF::Instance()->INPUT_ROW_TABLE.clear();
		PuzaDEF::Instance()->FEATURE_MAPPING.clear();
		unordered_map<string,int>::iterator map_iter;

		while(!InputFile.eof()){
			getline(InputFile,temp,'\n');
		    if(temp !=""){
		    	PuzaRow newRow;
		    	// read response
		    	std::istringstream iss(temp);
		    	getline(iss,temp,' ');
		    	newRow.response = from_string<double>(temp); // convert, require c++11
		    	getline(iss,temp,'\n');
		    	std::istringstream tempiss(temp);
		    	vector<string> tokens;
		    	copy(istream_iterator<string>(tempiss), istream_iterator<string>(), back_inserter<vector<string> >(tokens));
		    	if(tokens.size()<1){
		    		continue;
		    	}
		    	for (vector<string>::iterator iter = tokens.begin(); iter < tokens.end(); iter++){
		    		string raw_feature_id;
		    		PuzaRowItem newItem;
		    		std::istringstream valueiss((*iter));
		    		getline(valueiss,temp,':');
		    		raw_feature_id = temp;
		    		getline(valueiss,temp);
		    		double feature_value = from_string<double>(temp);
		    		// check feature mapping
		    		int target_feature_id = -1;
		    		map_iter = PuzaDEF::Instance()->FEATURE_MAPPING.find(raw_feature_id); // check and insert user id
		    		int USER_NUM = PuzaDEF::Instance()->FEATURE_MAPPING.size();
		    		if(map_iter == PuzaDEF::Instance()->FEATURE_MAPPING.end()){
		    			PuzaDEF::Instance()->FEATURE_MAPPING.insert(pair<string,int>(raw_feature_id, USER_NUM));
		    			target_feature_id = USER_NUM;
		    		}
		    		else{
		    			target_feature_id = (*map_iter).second;
		    		}
		    		newItem.feature_id = target_feature_id;
		    		newItem.feature_value = feature_value;
		    		newRow.features.push_back(newItem);
		    	}
		    	PuzaDEF::Instance()->INPUT_ROW_TABLE.push_back(newRow);
		    	TOTAL_N ++;
		    	if(TOTAL_N % 1000000 == 0){
		    		PuzaLogger::Instance()->PutString("Processed " + to_string<int>(TOTAL_N) + " rows.");
		    	}
		    }
		}
		InputFile.close();
		PuzaLogger::Instance()->PutString("Finished Reading File.");
		PuzaLogger::Instance()->PutString("Total number of rows:" + to_string<int>(TOTAL_N));
		PuzaLogger::Instance()->PutString("Total number of features:" + to_string<unsigned int>(PuzaDEF::Instance()->FEATURE_MAPPING.size()));
	}
}
#endif
